import type { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import axios from 'axios'
import qs from 'qs'
import { redirectToLogin } from '@/utils/biz'
import { Loader } from '@/utils/common'

import { saveFile } from '@/utils/biz'
import { ACCESS_TOKEN_KEY } from '@/constants'
import type { RequestOptionsType, ResponseDataType } from '@/types/request'

// axios.defaults.timeout = 60 * 1000
axios.defaults.baseURL = import.meta.env.DEV
  ? import.meta.env.VITE_API_BASE_URL
  : import.meta.env.VITE_API_ORIGIN + import.meta.env.VITE_API_BASE_URL
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'

let pendingRequestCount: number = 0 // 请求中数量
const pendingRequestList: string[] = [] // 请求中列表

/**
 *  获取请求标识
 */
const getRequestIdentifer = (config: InternalAxiosRequestConfig): string => {
  const { url, method, data, params } = config
  return `${url}_${method}_${JSON.stringify(params)}_${JSON.stringify(data)}`
}

/**
 *  添加请求到pendingRequestList中
 */
const addPendingRequest = (config: InternalAxiosRequestConfig) => {
  const identifer = getRequestIdentifer(config)
  pendingRequestList.push(identifer)
}

/**
 *  从pendingRequestList中移除请求
 */
const removePendingRequest = (config: InternalAxiosRequestConfig) => {
  const identifer = getRequestIdentifer(config)
  const index = pendingRequestList.findIndex((item) => item === identifer)
  pendingRequestList.splice(index, 1)
}

/**
 *  检测是否是重复请求，如果是直接取消本次请求
 */
const checkPendingRequest = (config: InternalAxiosRequestConfig) => {
  if (config.headers['Content-Type']?.toString().includes('multipart/form-data')) {
    return
  }
  const identifer = getRequestIdentifer(config)
  config.cancelToken = new axios.CancelToken((c) => {
    if (pendingRequestList.includes(identifer)) {
      c(`重复请求: ${identifer}`)
    } else {
      addPendingRequest(config)
    }
  })
}

/**
 *  前置拦截器
 */
axios.interceptors.request.use(
  (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
    checkPendingRequest(config)
    config.headers['X-Requested-With'] = 'XMLHttpRequest'
    const authorization = localStorage.getItem(ACCESS_TOKEN_KEY)
    if (authorization) {
      config.headers['Authorization'] = authorization
    }
    if (config.method === 'get' && config.data) {
      const queryString = qs.stringify(config.data)
      config.url += `?${queryString}`
    }
    pendingRequestCount++
    const hasLoader = config?.hasLoader
    if (hasLoader) {
      // 打开loader
      Loader.show()
    }
    return config
  },
  (error: AxiosError) => {
    return Promise.reject(error.message)
  }
)
/**
 *  后置拦截器
 */
axios.interceptors.response.use(
  async (response: AxiosResponse<ResponseDataType, unknown>) => {
    pendingRequestCount--
    removePendingRequest(response.config)
    if (pendingRequestCount === 0) {
      // 关闭loader
      Loader.hide()
    }
    const contentType = response.headers['content-type'] || ''
    const contentDisposition = response.headers['content-disposition']
    if (contentType.includes('application/json')) {
      const { success, message } = response.data
      if (!success) {
        window.$message.error(message)
        return Promise.reject(message)
      }
    } else if (contentDisposition) {
      if (response.config.download) {
        const fileName = decodeURIComponent(contentDisposition.split('=')[1])
        if (response.data instanceof Blob) {
          saveFile(response.data, fileName)
        }
      }
    }
    return response
  },
  (error: AxiosError<ResponseDataType>) => {
    pendingRequestCount--
    error.config && removePendingRequest(error.config)
    if (pendingRequestCount === 0) {
      // 关闭loader
      Loader.hide()
    }
    if (error.response?.status === 401) {
      setTimeout(() => {
        redirectToLogin()
      }, 2000)
    }
    const message = error.response?.data.message || error.message
    window.$message.error(message)
    return Promise.reject(error.message)
  }
)

/**
 *  请求方法类
 */
class Request {
  private static Fetch<T>(
    options: RequestOptionsType
  ): Promise<T extends Blob ? T : ResponseDataType<T>> {
    const { hasLoader = true, ...rest } = options
    return axios({
      hasLoader,
      ...rest
    })
      .then((res: AxiosResponse) => {
        return res && res.data
      })
      .catch((error) => {
        throw new Error(error)
      })
  }

  static Get<T>(options: RequestOptionsType) {
    return Request.Fetch<T>({ ...options, ...{ method: 'get' } })
  }

  static Post<T>(options: RequestOptionsType) {
    return Request.Fetch<T>({
      ...options,
      method: 'post'
    })
  }

  static Put<T>(options: RequestOptionsType) {
    return Request.Fetch<T>({
      ...options,
      method: 'put'
    })
  }

  static Delete<T>(options: RequestOptionsType) {
    return Request.Fetch<T>({
      ...options,
      method: 'delete'
    })
  }

  static Upload<T>(options: RequestOptionsType<Record<string, string | Blob>>) {
    const { data, ...rest } = options
    const formData = new FormData()
    if (data) {
      Object.keys(data).forEach((key) => {
        formData.append(key, data[key])
      })
    }
    return Request.Fetch<T>({
      ...rest,
      data: formData,
      headers: {
        'Content-Type': 'multipart/form-data'
      },
      method: 'post'
    })
  }
}

export default Request
